using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Serialization.Formatters;
using Colman.Rpg;
using System.Xml.Serialization;
using System.IO;
using System.Text;
using Colman.Rpg.Messaging;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Lifetime;

namespace Colman.Rpg.Server
{
    /// <summary>
    /// Server's connection handler (Using by remoting)
    /// </summary>
    public class ServerEndPoint : MarshalByRefObject, IServerProxy
    {
        /// <summary>
        /// holds connected users channels for callback messages
        /// </summary>
        private Dictionary<string, IClientCallback> registeredUsers = new Dictionary<string, IClientCallback>();

        /// <summary>
        /// Gets messages from clients, activates the messages' request and sends responses to client
        /// </summary>
        /// <param name="request">requested message from client</param>
        public void SendMessage(RequestMessage request)
        {
            // call the workflow to get all the responeses
            foreach (ResponseMessage response in WorkFlow.CreateResponses(request))
            {
                // if response is for everyone
                if (response.Destination == ResponseDestination.Everyone)
                {
                    List<IClientCallback> deadCallbacks = new List<IClientCallback>();

                    // goes over all connected users
                    foreach (IClientCallback client in registeredUsers.Values)
                    {
                        try
                        {
                            client.SendMessage(response);
                        }
                        catch (SocketException)
                        {
                            deadCallbacks.Add(client);
                        }
                    }

                    // Remove all dead callbacks
                    foreach (IClientCallback callback in deadCallbacks)
                    {
                        foreach (KeyValuePair<string, IClientCallback> pair in registeredUsers)
                        {
                            if (pair.Value == callback)
                            {
                                this.LogOffUser(pair.Key);
                                break;
                            }
                        }
                    }
                }
                // if message is for specific user
                else if (response.Destination == ResponseDestination.Manual)
                {
                    List<string> deadUsers = new List<string>();

                    // goes over all specific users from the response object
                    foreach (Player CurrUser in response.DestinationUsers)
                    {
                        try
                        {
                            if (registeredUsers.ContainsKey(CurrUser.Name))
                                registeredUsers[CurrUser.Name].SendMessage(response);
                        }
                        catch (SocketException)
                        {
                            deadUsers.Add(CurrUser.Name);
                        }
                    }

                    // Remove all dead users
                    foreach (string deadUser in deadUsers)
                    {
                        this.LogOffUser(deadUser);
                    }
                }
                // if message is for the requester
                else
                {
                    try
                    {
                        if (registeredUsers.ContainsKey(request.UserName))
                            registeredUsers[request.UserName].SendMessage(response);
                    }
                    catch (SocketException)
                    {
                        // User is dead. Log them off gracefully.
                        this.LogOffUser(request.UserName);
                    }
                }
            }
        }

        /// <summary>
        /// Gets users requests to login
        /// </summary>
        /// <param name="clientEntry">client object for callback</param>
        /// <param name="hashedPassword">client hashed password</param>
        public void LoginUser(IClientCallback clientEntry, string hashedPassword)
        {
            Console.WriteLine("Someone attempting to log in as " + clientEntry.UserName + ".");

            // trying to login the server
            Player player = Dal.Login(clientEntry.UserName, hashedPassword);

            // if login failed
            if (player == null)
                throw new LoginFailedException();

            // saves the logon user pipe 
            registeredUsers.Add(clientEntry.UserName, clientEntry);

            // saves the user data in server's players object.
            PlayersCache.RegisterUser(player);

            Console.WriteLine("Logged the user " + clientEntry.UserName + " in successfully.");
        }
        
        /// <summary>
        /// Gets user request to log off
        /// </summary>
        /// <param name="username">user name to log off</param>
        public void LogOffUser(string username)
        {
            // removes the logon user pipe 
            registeredUsers.Remove(username);

            // removes the user data from server's players object.
            PlayersCache.UnregisterUser(username);

            Console.WriteLine("Logged the user " + username + " out successfully.");
        }

        /// <summary>
        /// Notifies all clients of a shutdown.
        /// </summary>
        public void Shutdown()
        {
            ServerShutdownResponse response = new ServerShutdownResponse();

            foreach (IClientCallback callback in this.registeredUsers.Values)
            {
                try
                {
                    callback.SendMessage(response);
                }
                catch (Exception ex)
                {
                    // We don't care if the client is dead. Less work for us.
                }
            }

            foreach (string username in this.registeredUsers.Keys)
            {
                PlayersCache.UnregisterUser(username);
            }

            this.registeredUsers.Clear();
        }

        /// <summary>
        /// Sets server life time to infinity
        /// </summary>
        /// <returns></returns>
        public override object InitializeLifetimeService()
        {
            // Let me live for as long as I need to.
            ILease lease = (ILease)base.InitializeLifetimeService();

            if (lease.CurrentState == LeaseState.Initial)
            {
                lease.InitialLeaseTime = TimeSpan.Zero;
            }

            return lease;
        }
    }
}
